#include "src/pbc/portable-bytecode-array-iterator.h"

#include "src/pbc/portable-bytecode-array.h"

namespace portable {

BytecodeArrayIterator::BytecodeArrayIterator(BytecodeArray* bytecode_array,
                                             int initial_offset)
    : bytecode_array_(bytecode_array),
      constant_pool_(0),
      start_(reinterpret_cast<uint8_t*>(
          bytecode_array_->GetFirstBytecodeAddress())),
      end_(start_ + bytecode_array_->length()),
      cursor_(start_ + initial_offset),
      operand_scale_(OperandScale::kSingle),
      prefix_size_(0) {
  UpdateOperandScale();
}

BytecodeArrayIterator::BytecodeArrayIterator(const uint8_t* raw_array,
                                             int length,
                                             PbcConstantTable constant_pool)
    : bytecode_array_(nullptr),
      constant_pool_(constant_pool),
      start_(const_cast<uint8_t*>(raw_array)),
      end_(start_ + length),
      cursor_(start_),
      operand_scale_(OperandScale::kSingle),
      prefix_size_(0) {
  UpdateOperandScale();
}

void BytecodeArrayIterator::SetOffset(int offset) {
  if (offset < 0) return;
  cursor_ = reinterpret_cast<uint8_t*>(
      bytecode_array()->GetFirstBytecodeAddress() + offset);
  UpdateOperandScale();
}

// static
bool BytecodeArrayIterator::IsValidOffset(BytecodeArray* bytecode_array,
                                          int offset) {
  for (BytecodeArrayIterator it(bytecode_array); !it.done(); it.Advance()) {
    if (it.current_offset() == offset) return true;
    if (it.current_offset() > offset) break;
  }
  return false;
}

bool BytecodeArrayIterator::IsConstantAtIndexSmi(int index) const {
  if (!bytecode_array_) {
    if (constant_pool_.IsShortValue()) {
      return constant_pool_.ShortEntries()[index].Kind() ==
             PbcShortValueKind::kShortSmi;
    }
    return constant_pool_.Entries()[index].Kind() == PbcValueKind::kSmi;
  }
  return bytecode_array()->constant_pool()->IsSmi(index);
}

Smi BytecodeArrayIterator::GetConstantAtIndexAsSmi(int index) const {
  if (!bytecode_array_) {
    if (constant_pool_.IsShortValue()) {
      PbcShortValue entry = constant_pool_.ShortEntries()[index];
      return Smi::FromInt(static_cast<const PbcShortSmi&>(entry).GetValue());
    }
    PbcValue entry = constant_pool_.Entries()[index];
    return Smi::FromInt(static_cast<const SmiScalar&>(entry).GetValue());
  }
  return bytecode_array()->constant_pool()->GetSmi(index);
}

int BytecodeArrayIterator::GetRelativeJumpTargetOffset() const {
  Bytecode bytecode = current_bytecode();
  if (Bytecodes::IsJumpImmediate(bytecode)) {
    int relative_offset = GetUnsignedImmediateOperand(0);
    if (bytecode == Bytecode::kJumpLoop) {
      relative_offset = -relative_offset;
    }
    return relative_offset;
  } else if (Bytecodes::IsJumpConstant(bytecode)) {
    Smi smi = GetConstantAtIndexAsSmi(GetIndexOperand(0));
    return smi.value();
  } else {
    P_UNREACHABLE();
  }
}

int BytecodeArrayIterator::GetJumpTargetOffset() const {
  return GetAbsoluteOffset(GetRelativeJumpTargetOffset());
}

JumpTableTargetOffsets BytecodeArrayIterator::GetJumpTableTargetOffsets()
    const {
  uint32_t table_start, table_size;
  int32_t case_value_base;

  P_DCHECK_EQ(current_bytecode(), Bytecode::kSwitchOnSmiNoFeedback);
  table_start = GetIndexOperand(0);
  table_size = GetUnsignedImmediateOperand(1);
  case_value_base = GetImmediateOperand(2);

  return JumpTableTargetOffsets(this, table_start, table_size, case_value_base);
}

int BytecodeArrayIterator::GetAbsoluteOffset(int relative_offset) const {
  return current_offset() + relative_offset + prefix_size_;
}

int BytecodeArrayIterator::GetOffsetWithPrefix(int relative_offset) const {
  return current_offset() + relative_offset;
}

std::ostream& BytecodeArrayIterator::PrintTo(std::ostream& os) const {
  return BytecodeDecoder::Decode(os, cursor_ - prefix_size_);
}

void BytecodeArrayIterator::UpdatePointers() {
  uint8_t* start =
      reinterpret_cast<uint8_t*>(bytecode_array_->GetFirstBytecodeAddress());
  if (start != start_) {
    start_ = start;
    uint8_t* end = start + bytecode_array_->length();
    size_t distance_to_end = end_ - cursor_;
    cursor_ = end - distance_to_end;
    end_ = end;
  }
}

JumpTableTargetOffsets::JumpTableTargetOffsets(
    const BytecodeArrayIterator* iterator, int table_start, int table_size,
    int case_value_base)
    : iterator_(iterator),
      table_start_(table_start),
      table_size_(table_size),
      case_value_base_(case_value_base) {}

JumpTableTargetOffsets::iterator JumpTableTargetOffsets::begin() const {
  return iterator(case_value_base_, table_start_, table_start_ + table_size_,
                  iterator_);
}
JumpTableTargetOffsets::iterator JumpTableTargetOffsets::end() const {
  return iterator(case_value_base_ + table_size_, table_start_ + table_size_,
                  table_start_ + table_size_, iterator_);
}
int JumpTableTargetOffsets::size() const {
  int ret = 0;
  // TODO: Is there a more efficient way of doing this than iterating?
  for (JumpTableTargetOffset entry : *this) {
    (void)entry;
    ret++;
  }
  return ret;
}

JumpTableTargetOffsets::iterator::iterator(
    int case_value, int table_offset, int table_end,
    const BytecodeArrayIterator* iterator)
    : iterator_(iterator),
      current_(Smi::zero()),
      index_(case_value),
      table_offset_(table_offset),
      table_end_(table_end) {
  UpdateAndAdvanceToValid();
}

JumpTableTargetOffset JumpTableTargetOffsets::iterator::operator*() {
  P_DCHECK_LT(table_offset_, table_end_);
  return {index_, iterator_->GetAbsoluteOffset(Smi::ToInt(current_))};
}

JumpTableTargetOffsets::iterator&
JumpTableTargetOffsets::iterator::operator++() {
  P_DCHECK_LT(table_offset_, table_end_);
  ++table_offset_;
  ++index_;
  UpdateAndAdvanceToValid();
  return *this;
}

bool JumpTableTargetOffsets::iterator::operator!=(
    const JumpTableTargetOffsets::iterator& other) {
  P_DCHECK_EQ(iterator_, other.iterator_);
  P_DCHECK_EQ(table_end_, other.table_end_);
  P_DCHECK_EQ(index_ - other.index_, table_offset_ - other.table_offset_);
  return index_ != other.index_;
}

void JumpTableTargetOffsets::iterator::UpdateAndAdvanceToValid() {
  while (table_offset_ < table_end_ &&
         !iterator_->IsConstantAtIndexSmi(table_offset_)) {
    ++table_offset_;
    ++index_;
  }

  // Make sure we haven't reached the end of the table with a hole in current.
  if (table_offset_ < table_end_) {
    P_DCHECK(iterator_->IsConstantAtIndexSmi(table_offset_));
    current_ = iterator_->GetConstantAtIndexAsSmi(table_offset_);
  }
}

}  // namespace portable
